﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using iStudio.Structure.Engine;
using iStudio.Structure.Extension;

namespace iStudio.Structure.Control.Audio.Spectrum
{
    /// <summary>
    /// A spectrum analyzer control for visualizing audio level and frequency data.
    /// </summary>
    [DisplayName ("Spectrum"), Description ("Displays audio level and frequency data."), ToolboxItem (true), TemplatePart (Name = "PART_SpectrumCanvas", Type = typeof (Canvas))]
    public class Spectrum : System.Windows.Controls.Control
    {
        #region Fields

        private readonly DispatcherTimer _animationTimer;
        private readonly List<Shape> _barShapes = new List<Shape> ();
        private readonly List<Shape> _peakShapes = new List<Shape> ();
        private int[] _barIndexMax;
        private int[] _barLogScaleIndexMax;
        private double _barWidth = 1;
        private float[] _channelData = new float[2048];
        private float[] _channelPeakData;
        private int _maximumFrequencyIndex = 2047;
        private int _minimumFrequencyIndex;
        private ISpectrumPlayer _soundPlayer;
        private Canvas _spectrumCanvas;

        #endregion

        #region Constants

        private const int ScaleFactorLinear = 9;
        private const int ScaleFactorSqr = 2;
        private const double MinDbValue = -90;
        private const double MaxDbValue = 0;
        private const double DbScale = (MaxDbValue - MinDbValue);

        #endregion

        #region Dependency Properties

        #region MaximumFrequency

        /// <summary>
        /// Identifies the <see cref="MaximumFrequency" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty MaximumFrequencyProperty = DependencyProperty.Register ("MaximumFrequency", typeof (int), typeof (Spectrum), new UIPropertyMetadata (20000, OnMaximumFrequencyChanged, OnCoerceMaximumFrequency));

        /// <summary>
        /// Gets or sets the maximum display frequency (right side) for the spectrum analyzer.
        /// </summary>
        /// <remarks>In usual practice, this value should be somewhere between 0 and half of the maximum sample rate. If using
        /// the maximum sample rate, this would be roughly 22000.</remarks>
        [Category ("Common")]
        public int MaximumFrequency
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (int) GetValue (MaximumFrequencyProperty);
            }
            set
            {
                SetValue (MaximumFrequencyProperty, value);
            }
        }

        private static object OnCoerceMaximumFrequency (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceMaximumFrequency ((int) value);
            }
            return value;
        }

        private static void OnMaximumFrequencyChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnMaximumFrequencyChanged ((int) e.OldValue, (int) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="MaximumFrequency"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="MaximumFrequency"/></param>
        /// <returns>The adjusted value of <see cref="MaximumFrequency"/></returns>
        protected virtual int OnCoerceMaximumFrequency (int value)
        {
            if (value < MinimumFrequency)
            {
                return MinimumFrequency + 1;
            }
            return value;
        }

        /// <summary>
        /// Called after the <see cref="MaximumFrequency"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="MaximumFrequency"/></param>
        /// <param name="newValue">The new value of <see cref="MaximumFrequency"/></param>
        protected virtual void OnMaximumFrequencyChanged (int oldValue, int newValue)
        {
            UpdateBarLayout ();
        }

        #endregion

        #region Minimum Frequency

        /// <summary>
        /// Identifies the <see cref="MinimumFrequency" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty MinimumFrequencyProperty = DependencyProperty.Register ("MinimumFrequency", typeof (int), typeof (Spectrum), new UIPropertyMetadata (20, OnMinimumFrequencyChanged, OnCoerceMinimumFrequency));

        /// <summary>
        /// Gets or sets the minimum display frequency (left side) for the spectrum analyzer.
        /// </summary>
        [Category ("Common")]
        public int MinimumFrequency
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (int) GetValue (MinimumFrequencyProperty);
            }
            set
            {
                SetValue (MinimumFrequencyProperty, value);
            }
        }

        private static object OnCoerceMinimumFrequency (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceMinimumFrequency ((int) value);
            }
            return value;
        }

        private static void OnMinimumFrequencyChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnMinimumFrequencyChanged ((int) e.OldValue, (int) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="MinimumFrequency"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="MinimumFrequency"/></param>
        /// <returns>The adjusted value of <see cref="MinimumFrequency"/></returns>
        protected virtual int OnCoerceMinimumFrequency (int value)
        {
            if (value < 0)
            {
                return 0;
            }
            CoerceValue (MaximumFrequencyProperty);
            return value;
        }

        /// <summary>
        /// Called after the <see cref="MinimumFrequency"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="MinimumFrequency"/></param>
        /// <param name="newValue">The new value of <see cref="MinimumFrequency"/></param>
        protected virtual void OnMinimumFrequencyChanged (int oldValue, int newValue)
        {
            UpdateBarLayout ();
        }

        #endregion

        #region BarCount

        /// <summary>
        /// Identifies the <see cref="BarCount" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty BarCountProperty = DependencyProperty.Register ("BarCount", typeof (int), typeof (Spectrum), new UIPropertyMetadata (32, OnBarCountChanged, OnCoerceBarCount));

        /// <summary>
        /// Gets or sets the number of bars to show on the sprectrum analyzer.
        /// </summary>
        /// <remarks>A bar's width can be a minimum of 1 pixel. If the BarSpacing and BarCount property result
        /// in the bars being wider than the chart itself, the BarCount will automatically scale down.</remarks>
        [Category ("Common")]
        public int BarCount
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (int) GetValue (BarCountProperty);
            }
            set
            {
                SetValue (BarCountProperty, value);
            }
        }

        private static object OnCoerceBarCount (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceBarCount ((int) value);
            }
            return value;
        }

        private static void OnBarCountChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnBarCountChanged ((int) e.OldValue, (int) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="BarCount"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="BarCount"/></param>
        /// <returns>The adjusted value of <see cref="BarCount"/></returns>
        protected virtual int OnCoerceBarCount (int value)
        {
            value = Math.Max (value, 1);
            return value;
        }

        /// <summary>
        /// Called after the <see cref="BarCount"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="BarCount"/></param>
        /// <param name="newValue">The new value of <see cref="BarCount"/></param>
        protected virtual void OnBarCountChanged (int oldValue, int newValue)
        {
            UpdateBarLayout ();
        }

        #endregion

        #region BarSpacing

        /// <summary>
        /// Identifies the <see cref="BarSpacing" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty BarSpacingProperty = DependencyProperty.Register ("BarSpacing", typeof (double), typeof (Spectrum), new UIPropertyMetadata (5.0d, OnBarSpacingChanged, OnCoerceBarSpacing));

        /// <summary>
        /// Gets or sets the spacing between the bars.
        /// </summary>
        [Category ("Common")]
        public double BarSpacing
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (double) GetValue (BarSpacingProperty);
            }
            set
            {
                SetValue (BarSpacingProperty, value);
            }
        }

        private static object OnCoerceBarSpacing (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceBarSpacing ((double) value);
            }
            return value;
        }

        private static void OnBarSpacingChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnBarSpacingChanged ((double) e.OldValue, (double) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="BarSpacing"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="BarSpacing"/></param>
        /// <returns>The adjusted value of <see cref="BarSpacing"/></returns>
        protected virtual double OnCoerceBarSpacing (double value)
        {
            value = Math.Max (value, 0);
            return value;
        }

        /// <summary>
        /// Called after the <see cref="BarSpacing"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="BarSpacing"/></param>
        /// <param name="newValue">The new value of <see cref="BarSpacing"/></param>
        protected virtual void OnBarSpacingChanged (double oldValue, double newValue)
        {
            UpdateBarLayout ();
        }

        #endregion

        #region PeakFallDelay

        /// <summary>
        /// Identifies the <see cref="PeakFallDelay" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty PeakFallDelayProperty = DependencyProperty.Register ("PeakFallDelay", typeof (int), typeof (Spectrum), new UIPropertyMetadata (10, OnPeakFallDelayChanged, OnCoercePeakFallDelay));

        /// <summary>
        /// Gets or sets the delay factor for the peaks falling.
        /// </summary>
        /// <remarks>
        /// The delay is relative to the refresh rate of the chart.
        /// </remarks>
        [Category ("Common")]
        public int PeakFallDelay
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (int) GetValue (PeakFallDelayProperty);
            }
            set
            {
                SetValue (PeakFallDelayProperty, value);
            }
        }

        private static object OnCoercePeakFallDelay (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoercePeakFallDelay ((int) value);
            }
            return value;
        }

        private static void OnPeakFallDelayChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnPeakFallDelayChanged ((int) e.OldValue, (int) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="PeakFallDelay"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="PeakFallDelay"/></param>
        /// <returns>The adjusted value of <see cref="PeakFallDelay"/></returns>
        protected virtual int OnCoercePeakFallDelay (int value)
        {
            value = Math.Max (value, 0);
            return value;
        }

        /// <summary>
        /// Called after the <see cref="PeakFallDelay"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="PeakFallDelay"/></param>
        /// <param name="newValue">The new value of <see cref="PeakFallDelay"/></param>
        protected virtual void OnPeakFallDelayChanged (int oldValue, int newValue)
        {
        }

        #endregion

        #region IsFrequencyScaleLinear

        /// <summary>
        /// Identifies the <see cref="IsFrequencyScaleLinear" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty IsFrequencyScaleLinearProperty = DependencyProperty.Register ("IsFrequencyScaleLinear", typeof (bool), typeof (Spectrum), new UIPropertyMetadata (false, OnIsFrequencyScaleLinearChanged, OnCoerceIsFrequencyScaleLinear));

        /// <summary>
        /// Gets or sets a value indicating whether the bars are layed out on a linear scale horizontally.
        /// </summary>
        /// <remarks>
        /// If true, the bars will represent frequency buckets on a linear scale (making them all
        /// have equal band widths on the frequency scale). Otherwise, the bars will be layed out
        /// on a logrithmic scale, with each bar having a larger bandwidth than the one previous.
        /// </remarks>
        [Category ("Common")]
        public bool IsFrequencyScaleLinear
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (bool) GetValue (IsFrequencyScaleLinearProperty);
            }
            set
            {
                SetValue (IsFrequencyScaleLinearProperty, value);
            }
        }

        private static object OnCoerceIsFrequencyScaleLinear (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceIsFrequencyScaleLinear ((bool) value);
            }
            return value;
        }

        private static void OnIsFrequencyScaleLinearChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnIsFrequencyScaleLinearChanged ((bool) e.OldValue, (bool) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="IsFrequencyScaleLinear"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="IsFrequencyScaleLinear"/></param>
        /// <returns>The adjusted value of <see cref="IsFrequencyScaleLinear"/></returns>
        protected virtual bool OnCoerceIsFrequencyScaleLinear (bool value)
        {
            return value;
        }

        /// <summary>
        /// Called after the <see cref="IsFrequencyScaleLinear"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="IsFrequencyScaleLinear"/></param>
        /// <param name="newValue">The new value of <see cref="IsFrequencyScaleLinear"/></param>
        protected virtual void OnIsFrequencyScaleLinearChanged (bool oldValue, bool newValue)
        {
            UpdateBarLayout ();
        }

        #endregion

        #region BarHeightScaling

        /// <summary>
        /// Identifies the <see cref="BarHeightScaling" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty BarHeightScalingProperty = DependencyProperty.Register ("BarHeightScaling", typeof (BarHeightScalingStyles), typeof (Spectrum), new UIPropertyMetadata (BarHeightScalingStyles.Decibel, OnBarHeightScalingChanged, OnCoerceBarHeightScaling));

        /// <summary>
        /// Gets or sets a value indicating to what scale the bar heights are drawn.
        /// </summary>
        [Category ("Common")]
        public BarHeightScalingStyles BarHeightScaling
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (BarHeightScalingStyles) GetValue (BarHeightScalingProperty);
            }
            set
            {
                SetValue (BarHeightScalingProperty, value);
            }
        }

        private static object OnCoerceBarHeightScaling (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceBarHeightScaling ((BarHeightScalingStyles) value);
            }
            return value;
        }

        private static void OnBarHeightScalingChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnBarHeightScalingChanged ((BarHeightScalingStyles) e.OldValue, (BarHeightScalingStyles) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="BarHeightScaling"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="BarHeightScaling"/></param>
        /// <returns>The adjusted value of <see cref="BarHeightScaling"/></returns>
        protected virtual BarHeightScalingStyles OnCoerceBarHeightScaling (BarHeightScalingStyles value)
        {
            return value;
        }

        /// <summary>
        /// Called after the <see cref="BarHeightScaling"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="BarHeightScaling"/></param>
        /// <param name="newValue">The new value of <see cref="BarHeightScaling"/></param>
        protected virtual void OnBarHeightScalingChanged (BarHeightScalingStyles oldValue, BarHeightScalingStyles newValue)
        {
        }

        #endregion

        #region AveragePeaks

        /// <summary>
        /// Identifies the <see cref="AveragePeaks" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty AveragePeaksProperty = DependencyProperty.Register ("AveragePeaks", typeof (bool), typeof (Spectrum), new UIPropertyMetadata (false, OnAveragePeaksChanged, OnCoerceAveragePeaks));

        /// <summary>
        /// Gets or sets a value indicating whether each bar's peak 
        /// value will be averaged with the previous bar's peak.
        /// This creates a smoothing effect on the bars.
        /// </summary>
        [Category ("Common")]
        public bool AveragePeaks
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (bool) GetValue (AveragePeaksProperty);
            }
            set
            {
                SetValue (AveragePeaksProperty, value);
            }
        }

        private static object OnCoerceAveragePeaks (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceAveragePeaks ((bool) value);
            }
            return value;
        }

        private static void OnAveragePeaksChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnAveragePeaksChanged ((bool) e.OldValue, (bool) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="AveragePeaks"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="AveragePeaks"/></param>
        /// <returns>The adjusted value of <see cref="AveragePeaks"/></returns>
        protected virtual bool OnCoerceAveragePeaks (bool value)
        {
            return value;
        }

        /// <summary>
        /// Called after the <see cref="AveragePeaks"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="AveragePeaks"/></param>
        /// <param name="newValue">The new value of <see cref="AveragePeaks"/></param>
        protected virtual void OnAveragePeaksChanged (bool oldValue, bool newValue)
        {
        }

        #endregion

        #region BarStyle

        /// <summary>
        /// Identifies the <see cref="BarStyle" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty BarStyleProperty = DependencyProperty.Register ("BarStyle", typeof (Style), typeof (Spectrum), new UIPropertyMetadata (null, OnBarStyleChanged, OnCoerceBarStyle));

        /// <summary>
        /// Gets or sets a style with which to draw the bars on the spectrum analyzer.
        /// </summary>
        public Style BarStyle
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (Style) GetValue (BarStyleProperty);
            }
            set
            {
                SetValue (BarStyleProperty, value);
            }
        }

        private static object OnCoerceBarStyle (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceBarStyle ((Style) value);
            }
            return value;
        }

        private static void OnBarStyleChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnBarStyleChanged ((Style) e.OldValue, (Style) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="BarStyle"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="BarStyle"/></param>
        /// <returns>The adjusted value of <see cref="BarStyle"/></returns>
        protected virtual Style OnCoerceBarStyle (Style value)
        {
            return value;
        }

        /// <summary>
        /// Called after the <see cref="BarStyle"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="BarStyle"/></param>
        /// <param name="newValue">The new value of <see cref="BarStyle"/></param>
        protected virtual void OnBarStyleChanged (Style oldValue, Style newValue)
        {
            UpdateBarLayout ();
        }

        #endregion

        #region PeakStyle

        /// <summary>
        /// Identifies the <see cref="PeakStyle" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty PeakStyleProperty = DependencyProperty.Register ("PeakStyle", typeof (Style), typeof (Spectrum), new UIPropertyMetadata (null, OnPeakStyleChanged, OnCoercePeakStyle));

        /// <summary>
        /// Gets or sets a style with which to draw the falling peaks on the spectrum analyzer.
        /// </summary>
        [Category ("Common")]
        public Style PeakStyle
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (Style) GetValue (PeakStyleProperty);
            }
            set
            {
                SetValue (PeakStyleProperty, value);
            }
        }

        private static object OnCoercePeakStyle (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoercePeakStyle ((Style) value);
            }
            return value;
        }

        private static void OnPeakStyleChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnPeakStyleChanged ((Style) e.OldValue, (Style) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="PeakStyle"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="PeakStyle"/></param>
        /// <returns>The adjusted value of <see cref="PeakStyle"/></returns>
        protected virtual Style OnCoercePeakStyle (Style value)
        {
            return value;
        }

        /// <summary>
        /// Called after the <see cref="PeakStyle"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="PeakStyle"/></param>
        /// <param name="newValue">The new value of <see cref="PeakStyle"/></param>
        protected virtual void OnPeakStyleChanged (Style oldValue, Style newValue)
        {
            UpdateBarLayout ();
        }

        #endregion

        #region ActualBarWidth

        /// <summary>
        /// Identifies the <see cref="ActualBarWidth" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty ActualBarWidthProperty = DependencyProperty.Register ("ActualBarWidth", typeof (double), typeof (Spectrum), new UIPropertyMetadata (0.0d, OnActualBarWidthChanged, OnCoerceActualBarWidth));

        /// <summary>
        /// Gets the actual width that the bars will be drawn at.
        /// </summary>
        public double ActualBarWidth
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (double) GetValue (ActualBarWidthProperty);
            }
            protected set
            {
                SetValue (ActualBarWidthProperty, value);
            }
        }

        private static object OnCoerceActualBarWidth (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceActualBarWidth ((double) value);
            }
            return value;
        }

        private static void OnActualBarWidthChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnActualBarWidthChanged ((double) e.OldValue, (double) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="ActualBarWidth"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="ActualBarWidth"/></param>
        /// <returns>The adjusted value of <see cref="ActualBarWidth"/></returns>
        protected virtual double OnCoerceActualBarWidth (double value)
        {
            return value;
        }

        /// <summary>
        /// Called after the <see cref="ActualBarWidth"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="ActualBarWidth"/></param>
        /// <param name="newValue">The new value of <see cref="ActualBarWidth"/></param>
        protected virtual void OnActualBarWidthChanged (double oldValue, double newValue)
        {
        }

        #endregion

        #region RefreshRate

        /// <summary>
        /// Identifies the <see cref="RefreshInterval" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty RefreshIntervalProperty = DependencyProperty.Register ("RefreshInterval", typeof (int), typeof (Spectrum), new UIPropertyMetadata (25, OnRefreshIntervalChanged, OnCoerceRefreshInterval));

        /// <summary>
        /// Gets or sets the refresh interval, in milliseconds, of the Spectrum Analyzer.
        /// </summary>
        /// <remarks>
        /// The valid range of the interval is 10 milliseconds to 1000 milliseconds.
        /// </remarks>
        [Category ("Common")]
        public int RefreshInterval
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (int) GetValue (RefreshIntervalProperty);
            }
            set
            {
                SetValue (RefreshIntervalProperty, value);
            }
        }

        private static object OnCoerceRefreshInterval (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceRefreshInterval ((int) value);
            }
            return value;
        }

        private static void OnRefreshIntervalChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnRefreshIntervalChanged ((int) e.OldValue, (int) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="RefreshInterval"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="RefreshInterval"/></param>
        /// <returns>The adjusted value of <see cref="RefreshInterval"/></returns>
        protected virtual int OnCoerceRefreshInterval (int value)
        {
            value = Math.Min (1000, Math.Max (10, value));
            return value;
        }

        /// <summary>
        /// Called after the <see cref="RefreshInterval"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="RefreshInterval"/></param>
        /// <param name="newValue">The new value of <see cref="RefreshInterval"/></param>
        protected virtual void OnRefreshIntervalChanged (int oldValue, int newValue)
        {
            _animationTimer.Interval = TimeSpan.FromMilliseconds (newValue);
        }

        #endregion

        #region FFTComplexity

        /// <summary>
        /// Identifies the <see>
        ///                  <cref>FFTComplexity</cref>
        ///                </see> dependency property. 
        /// </summary>
        public static readonly DependencyProperty FftComplexityProperty = DependencyProperty.Register ("FFTComplexity", typeof (FftDataSize), typeof (Spectrum), new UIPropertyMetadata (FftDataSize.Fft2048, OnFftComplexityChanged, OnCoerceFftComplexity));

        /// <summary>
        /// Gets or sets the complexity of FFT results the Spectrum Analyzer expects. Larger values
        /// will be more accurate at converting time domain data to frequency data, but slower.
        /// </summary>
        [Category ("Common")]
        public FftDataSize FftComplexity
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (FftDataSize) GetValue (FftComplexityProperty);
            }
            set
            {
                SetValue (FftComplexityProperty, value);
            }
        }

        private static object OnCoerceFftComplexity (DependencyObject o, object value)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                return spectrum.OnCoerceFftComplexity ((FftDataSize) value);
            }
            return value;
        }

        private static void OnFftComplexityChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Spectrum spectrum = o as Spectrum;
            if (spectrum != null)
            {
                spectrum.OnFftComplexityChanged ((FftDataSize) e.OldValue, (FftDataSize) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see>
        ///                        <cref>FFTComplexity</cref>
        ///                      </see> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see>
        ///                                                 <cref>FFTComplexity</cref>
        ///                                               </see> </param>
        /// <returns>The adjusted value of <see>
        ///                                  <cref>FFTComplexity</cref>
        ///                                </see> </returns>
        protected virtual FftDataSize OnCoerceFftComplexity (FftDataSize value)
        {
            return value;
        }

        /// <summary>
        /// Called after the <see>
        ///                    <cref>FFTComplexity</cref>
        ///                  </see> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see>
        ///                                                <cref>FFTComplexity</cref>
        ///                                              </see> </param>
        /// <param name="newValue">The new value of <see>
        ///                                           <cref>FFTComplexity</cref>
        ///                                         </see> </param>
        protected virtual void OnFftComplexityChanged (FftDataSize oldValue, FftDataSize newValue)
        {
            _channelData = new float[((int) newValue / 2)];
        }

        #endregion

        #endregion

        #region Template Overrides

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code
        /// or internal processes call System.Windows.FrameworkElement.ApplyTemplate().
        /// </summary>
        public override void OnApplyTemplate ()
        {
            _spectrumCanvas = GetTemplateChild ("PART_SpectrumCanvas") as Canvas;
            if (_spectrumCanvas != null)
            {
                _spectrumCanvas.SizeChanged += SpectrumCanvasSizeChanged;
            }
            UpdateBarLayout ();
        }

        /// <summary>
        /// Called whenever the control's template changes. 
        /// </summary>
        /// <param name="oldTemplate">The old template</param>
        /// <param name="newTemplate">The new template</param>
        protected override void OnTemplateChanged (ControlTemplate oldTemplate, ControlTemplate newTemplate)
        {
            base.OnTemplateChanged (oldTemplate, newTemplate);
            if (_spectrumCanvas != null)
            {
                _spectrumCanvas.SizeChanged -= SpectrumCanvasSizeChanged;
            }
        }

        #endregion

        #region Constructors

        static Spectrum ()
        {
            DefaultStyleKeyProperty.OverrideMetadata (typeof (Spectrum), new FrameworkPropertyMetadata (typeof (Spectrum)));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Spectrum"/> class.
        /// </summary>
        public Spectrum ()
        {
            _animationTimer = new DispatcherTimer (DispatcherPriority.ApplicationIdle)
                                 {
                                     Interval = TimeSpan.FromMilliseconds (25),
                                 };
            _animationTimer.Tick += AnimationTimerTick;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Register a sound player from which the spectrum analyzer
        /// can get the necessary playback data.
        /// </summary>
        /// <param name="soundPlayer">A sound player that provides spectrum data through the ISpectrumPlayer interface methods.</param>
        public void RegisterSoundPlayer (ISpectrumPlayer soundPlayer)
        {
            _soundPlayer = soundPlayer;
            soundPlayer.PropertyChanged += SoundPlayerPropertyChanged;
            UpdateBarLayout ();
            _animationTimer.Start ();
        }

        #endregion

        #region Event Overrides

        /// <summary>
        /// When overridden in a derived class, participates in rendering operations that are directed by the layout system. 
        /// The rendering instructions for this element are not used directly when this method is invoked, and are 
        /// instead preserved for later asynchronous use by layout and drawing.
        /// </summary>
        /// <param name="dc">The drawing instructions for a specific element. This context is provided to the layout system.</param>
        protected override void OnRender (DrawingContext dc)
        {
            base.OnRender (dc);
            UpdateBarLayout ();
            UpdateSpectrum ();
        }

        /// <summary>
        /// Raises the SizeChanged event, using the specified information as part of the eventual event data.
        /// </summary>
        /// <param name="sizeInfo">Details of the old and new size involved in the change.</param>
        protected override void OnRenderSizeChanged (SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged (sizeInfo);
            UpdateBarLayout ();
            UpdateSpectrum ();
        }

        #endregion

        #region Private Drawing Methods

        private void UpdateSpectrum ()
        {
            if (_soundPlayer == null || _spectrumCanvas == null || _spectrumCanvas.RenderSize.Width < 1 || _spectrumCanvas.RenderSize.Height < 1)
            {
                return;
            }

            if ((_soundPlayer.IsPlaying || _soundPlayer.IsRecording) && !_soundPlayer.GetFftData (_channelData))
            {
                return;
            }

            UpdateSpectrumShapes ();
        }


        private void UpdateSpectrumShapes ()
        {
            bool allZero = true;
            double fftBucketHeight = 0f;
            double barHeight = 0f;
            double lastPeakHeight = 0f;
            double height = _spectrumCanvas.RenderSize.Height;
            int barIndex = 0;
            double peakDotHeight = Math.Max (_barWidth / 2.0f, 1);
            double barHeightScale = (height - peakDotHeight);

            for (int i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++)
            {
                // If we're paused, keep drawing, but set the current height to 0 so the peaks fall.
                if (!_soundPlayer.IsPlaying && !_soundPlayer.IsRecording)
                {
                    barHeight = 0f;
                }
                else // Draw the maximum value for the bar's band
                {
                    switch (BarHeightScaling)
                    {
                        case BarHeightScalingStyles.Decibel:
                            double dbValue = 20 * Math.Log10 (_channelData[i]);
                            fftBucketHeight = ((dbValue - MinDbValue) / DbScale) * barHeightScale;
                            break;
                        case BarHeightScalingStyles.Linear:
                            fftBucketHeight = (_channelData[i] * ScaleFactorLinear) * barHeightScale;
                            break;
                        case BarHeightScalingStyles.Sqrt:
                            fftBucketHeight = (((Math.Sqrt (_channelData[i])) * ScaleFactorSqr) * barHeightScale);
                            break;
                    }

                    if (barHeight < fftBucketHeight)
                    {
                        barHeight = fftBucketHeight;
                    }
                    if (barHeight < 0f)
                    {
                        barHeight = 0f;
                    }
                }

                // If this is the last FFT bucket in the bar's group, draw the bar.
                int currentIndexMax = IsFrequencyScaleLinear ? _barIndexMax[barIndex] : _barLogScaleIndexMax[barIndex];
                if (i == currentIndexMax)
                {
                    // Peaks can't surpass the height of the control.
                    if (barHeight > height)
                    {
                        barHeight = height;
                    }

                    if (AveragePeaks && barIndex > 0)
                    {
                        barHeight = (lastPeakHeight + barHeight) / 2;
                    }

                    double peakYPos = barHeight;

                    if (_channelPeakData[barIndex] < peakYPos)
                    {
                        _channelPeakData[barIndex] = (float) peakYPos;
                    }
                    else
                    {
                        _channelPeakData[barIndex] = (float) (peakYPos + (PeakFallDelay * _channelPeakData[barIndex])) / ((PeakFallDelay + 1));
                    }

                    double xCoord = BarSpacing + (_barWidth * barIndex) + (BarSpacing * barIndex) + 1;

                    _barShapes[barIndex].Margin = new Thickness (xCoord, (height - 1) - barHeight, 0, 0);
                    _barShapes[barIndex].Height = barHeight;
                    _peakShapes[barIndex].Margin = new Thickness (xCoord, (height - 1) - _channelPeakData[barIndex] - peakDotHeight, 0, 0);
                    _peakShapes[barIndex].Height = peakDotHeight;

                    if (_channelPeakData[barIndex] > 0.05)
                    {
                        allZero = false;
                    }

                    lastPeakHeight = barHeight;
                    barHeight = 0f;
                    barIndex++;
                }
            }

            if (allZero && !_soundPlayer.IsPlaying && !_soundPlayer.IsRecording)
            {
                _animationTimer.Stop ();
            }
        }

        private void UpdateBarLayout ()
        {
            if (_soundPlayer == null || _spectrumCanvas == null)
            {
                return;
            }

            _barWidth = Math.Max (((_spectrumCanvas.RenderSize.Width - (BarSpacing * (BarCount + 1))) / BarCount), 1);
            _maximumFrequencyIndex = Math.Min (_soundPlayer.GetFftFrequencyIndex (MaximumFrequency) + 1, 2047);
            _minimumFrequencyIndex = Math.Min (_soundPlayer.GetFftFrequencyIndex (MinimumFrequency), 2047);

            int actualBarCount = _barWidth >= 1.0d ? BarCount : Math.Max ((int) ((_spectrumCanvas.RenderSize.Width - BarSpacing) / (_barWidth + BarSpacing)), 1);
            _channelPeakData = new float[actualBarCount];

            int indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex;
            int linearIndexBucketSize = (int) Math.Round (indexCount / (double) actualBarCount, 0);
            List<int> maxIndexList = new List<int> ();
            List<int> maxLogScaleIndexList = new List<int> ();
            double maxLog = Math.Log (actualBarCount, actualBarCount);
            for (int i = 1; i < actualBarCount; i++)
            {
                maxIndexList.Add (_minimumFrequencyIndex + (i * linearIndexBucketSize));
                int logIndex = (int) ((maxLog - Math.Log ((actualBarCount + 1) - i, (actualBarCount + 1))) * indexCount) + _minimumFrequencyIndex;
                maxLogScaleIndexList.Add (logIndex);
            }
            maxIndexList.Add (_maximumFrequencyIndex);
            maxLogScaleIndexList.Add (_maximumFrequencyIndex);
            _barIndexMax = maxIndexList.ToArray ();
            _barLogScaleIndexMax = maxLogScaleIndexList.ToArray ();

            _spectrumCanvas.Children.Clear ();
            _barShapes.Clear ();
            _peakShapes.Clear ();

            double height = _spectrumCanvas.RenderSize.Height;
            double peakDotHeight = Math.Max (_barWidth / 2.0f, 1);
            for (int i = 0; i < actualBarCount; i++)
            {
                double xCoord = BarSpacing + (_barWidth * i) + (BarSpacing * i) + 1;
                Rectangle barRectangle = new Rectangle
                                             {
                                                 Margin = new Thickness (xCoord, height, 0, 0),
                                                 Width = _barWidth,
                                                 Height = 0,
                                                 Style = BarStyle
                                             };
                _barShapes.Add (barRectangle);
                Rectangle peakRectangle = new Rectangle
                                              {
                                                  Margin = new Thickness (xCoord, height - peakDotHeight, 0, 0),
                                                  Width = _barWidth,
                                                  Height = peakDotHeight,
                                                  Style = PeakStyle
                                              };
                _peakShapes.Add (peakRectangle);
            }

            foreach (Shape shape in _barShapes)
            {
                _spectrumCanvas.Children.Add (shape);
            }
            foreach (Shape shape in _peakShapes)
            {
                _spectrumCanvas.Children.Add (shape);
            }

            ActualBarWidth = _barWidth;
        }

        #endregion

        #region Event Handlers

        private void SoundPlayerPropertyChanged (object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "IsPlaying":
                case "IsRecording":
                    if ((_soundPlayer.IsPlaying || _soundPlayer.IsRecording) && !_animationTimer.IsEnabled)
                    {
                        _animationTimer.Start ();
                    }
                    break;
            }
        }

        private void AnimationTimerTick (object sender, EventArgs e)
        {
            UpdateSpectrum ();
        }

        private void SpectrumCanvasSizeChanged (object sender, SizeChangedEventArgs e)
        {
            UpdateBarLayout ();
        }

        #endregion
    }
}